/*
--------------------------------------------已完工------------------------------------------------------
*/
#include<iostream>
#include<Windows.h>
#include <conio.h>
using namespace std;

#define H 28
#define W 60
const int dir[4][2]{
	{-1,0},//上移//{x,y}即data[H][W]的y(=H),x(=W)里的y-1而x不变
	{0,1},//右移
	{1,0},//下移//在计算机中表示二维界面时默认使用笛卡尔坐标系，即x轴指向水平向左，y轴指向垂直向下
	{0,-1}//左移
};
enum BlockType{	
	EMPTY = 0,
    FOOD = 1,
};//枚举变量型，定义每个格子的状态
struct Map {
	BlockType data[H][W];//高即是行，宽即是列
	bool hasfood;
};//对地图内部自立一个动态数据类型表示地图状态
struct Pos {
	int x;
	int y;
};
struct Snake {
	Pos snake[H * W];//存放蛇的的身体数据的一维结构体数组，表示这样游戏的🐍最多铺满地图，长度超出要考虑溢出导致的错误
	                 //Pos类型的结构体数组，表示一维数组的每一个元素都具有int x,y两种属性，用于表示🐍身体的每一个元素的位置
	int snakedir;//🐍的方向
	int snakeLength;//蛇的长度
	int lastMoveTime=0;//上一次键入操作指令的时间
	int MoveFreqance=500;//计算机自动移动蛇体这个操作延长的时间

};//蛇的结构体

void iniSnake(Snake* snk) {
	snk->snakedir = 1;
	snk->snakeLength = 6;
	snk->snake[0] = {W/2,H/2};
	snk->snake[1] = { W / 2-1,H / 2 };
	snk->snake[2] = { W / 2-2,H / 2 };
}//🐍的初始化

void drawUnit(Pos p,const char unit[]) {
	COORD coord;
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	coord.X = p.x + 1;
	coord.Y = p.y + 1;
	SetConsoleCursorPosition(hOutput, coord);
	cout << unit;
}

void hideCursor() {
		HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
		CONSOLE_CURSOR_INFO curInfo = { 1, FALSE };
		SetConsoleCursorInfo( hOutput,& curInfo);
}//隐藏光标的函数

void initMap(Map *map) {
	for (int i = 0; i < H; ++i) {
		for (int j = 0; j < W; ++j) {
			map->data[i][j] = BlockType::EMPTY;
		}
	}
	map->hasfood = false;//地图的hasfood数据即为0，表示没有食物，与上方的map->data[][]保持一致
}//初始化地图的函数。传进来一个指针才能改变实际地图状态，不然函数结束修改的值会被释放

void drawMap(Map* map) {
	system("cls");   //C++的输出界面清屏语句
	cout << "┏";    //制表符号     ┓━ ┛┛┃┃━┗
	for (int i = 0; i < W; ++i) {
		cout << "━";
	}
	cout << "┓" << endl;

	for (int y = 0; y < H; ++y) {
		cout << "┃";
		for (int x = 0; x < W; ++x) {
			if (map->data[y][x] == BlockType::EMPTY) {
				cout << " ";
			}
		}
		cout << "┃"<<endl;
	}

	cout << "┗";
	for (int i = 0; i < W; ++i) {
		cout << "━";
	}
	cout << "┛" ;
}//绘制地图的函数

void drawsnake(Snake*snk) {
	
	for (int i = 0; i < snk->snakeLength; ++i) {
		drawUnit(snk->snake[i],"■");
	}
}//蛇体绘制函数

void checkChangeDir(Snake* snk) {
	if (_kbhit()) {
		switch(_getch()) {
		    case 'w':
				if (snk->snakedir != 2) {
					snk->snakedir = 0;
				}
			    break;
			case 'd':
				if (snk->snakedir != 3) {
					snk->snakedir = 1;
				}
				break;	
			case 's':
				if (snk->snakedir != 0) {
					snk->snakedir = 2;
				}
				break;
		    
			case 'a':
				if (snk->snakedir != 1) {
					snk->snakedir = 3;
				}
				break;
			default:
				break;
		}
	}
}

void movesnake(Snake* snk) {


	for (int i = snk->snakeLength - 1; i >= 1; --i) {
		snk->snake[i] = snk->snake[i - 1];
	}//到这里时间复杂度算上函数调用就已经是n^2了，所以贪吃蛇最好用链表实现，学过数据结构后回来改进一下代码

	snk->snake[0].y += dir[snk->snakedir][0];
	snk->snake[0].x += dir[snk->snakedir][1];
}//为了让🐍向蛇头方向前进一格

bool checkoutoffbound(Pos p) {
	return(p.x <=0 || p.x >= W + 1 || p.y <=0 || p.y >= H + 1);
	//当或运算都不满足时证明没有出界，返回0；反之若出界则返回1（=true）；
}//边界检查函数

void checkEatFood(Snake*snk,Pos tail,Map*map) {
	Pos head = snk->snake[0];
	if (map->data[head.y][head.x] == BlockType::FOOD) {
		snk->snake[snk->snakeLength++] = tail;
		map->data[head.y][head.x] = BlockType::EMPTY;
		map->hasfood = false;
		drawUnit(tail, "■");
	}
}

bool domove(Snake* snk, Map* map) {
	Pos tail = snk->snake[snk->snakeLength - 1];
	drawUnit(tail, " ");//取出snk蛇体数组最后一个数据将他抹去表示尾部前进一格

	movesnake(snk);     //为了让🐍向蛇头方向前进一格//因为蛇头的改变方式特殊，所以封装一个函数后调用，保证函数的可读性
	if (true == checkoutoffbound(snk->snake[0])) {
		system("cls");  //C++的输出界面清屏语句
		return false;
	}                   //调用边界检测函数
	checkEatFood( snk , tail, map );
	drawUnit(snk->snake [0],"■"); //在蛇头前进的地方画出来■表示🐍头前进一格

	return true;
}//执行移动操作的函数
bool checkSnakemove(Snake* snk, Map* map) {
	int curTime = GetTickCount();
	if (curTime - snk->lastMoveTime > snk->MoveFreqance) {
		if (false == domove(snk, map)) 
			return false;
		snk->lastMoveTime = curTime;
	}
	return true;
}//checksnakemove函数不直接对蛇体进行移动操作，是为了对蛇体的移动进行速度限制，防止计算机运行过快导致控制困难

void checkFoodGenerate(Snake*snk,Map*map) {
	if (false == map->hasfood) {
		while (1) {
			int x = rand() % W;//rand()表示生成一个随机数。rand（）%W表示生一个0--（W-1）的随机数
			int y = rand() % H;
			int i = 0;
			while (i < snk->snakeLength) //其实本质就是对每一个现有的蛇身元素与随机元素进行判断随机数是否与蛇身重合
			{
				if (snk->snake[i].x == x && snk->snake[i].y == y)//即生成的随机元素与现在蛇身的元素重合就会跳出当前while循环并重新生成随机数
				{
					break;
				}
				i++;
			}
			while (i==snk->snakeLength) //表示经过i=Length次判断后随机数都不和蛇身元素重合此时就可以对这个新生成的随机元素进行绘制
			{
				map->data[y][x] = BlockType::FOOD;
				map->hasfood = true;
				drawUnit({ x,y }, "●");
				return;
			}
		}
	}
}

void iniGame(Snake* snk, Map* map) {
	hideCursor();//调用隐藏光标函数
	initMap(map);
	iniSnake(snk);
	drawMap(map);
	drawsnake(snk);
}//游戏初始化函数，主要是为了不在主函数里写太多调用函数的语句，为了代码可观

int main() {
	Map map;
	Snake snk;
	iniGame(&snk, &map);//初始化游戏
	while (1) {
		checkChangeDir(&snk);
		if (false == checkSnakemove(&snk, &map)) {
			break;
		}
		checkFoodGenerate(&snk,&map);
	} 
	drawUnit({W/2-4,H/2},"Game Over");//游戏失败的界面绘制
	while (1) {}

	return 0;
}